package com.weareint.common.beans;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
@ConditionalOnBean(RedisTemplate.class)
public class RedisLockUtil {

    @Autowired
    private StringRedisTemplate redisTemplate;

    private final UUID id = UUID.randomUUID();

    private final String LUA_LOCK = "if (redis.call('exists', KEYS[1]) == 0) then " +
            "redis.call('hset', KEYS[1], ARGV[2], 1); " +
            "redis.call('pexpire', KEYS[1], ARGV[1]); " +
            "return nil; " +
            "end; " +
            "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
            "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
            "redis.call('pexpire', KEYS[1], ARGV[1]); " +
            "return nil; " +
            "end; " +
            "return redis.call('pttl', KEYS[1]);";
    private final String LUA_UNLOCK = "if (redis.call('exists', KEYS[1]) == 0) then " +
            "return 1; " +
            "end;" +
            "if (redis.call('hexists', KEYS[1], ARGV[2]) == 0) then " +
            "return nil;" +
            "end; " +
            "local counter = redis.call('hincrby', KEYS[1], ARGV[2], -1); " +
            "if (counter > 0) then " +
            "redis.call('pexpire', KEYS[1], ARGV[1]); " +
            "return 0; " +
            "else " +
            "redis.call('del', KEYS[1]); " +
            "return 1; "+
            "end; " +
            "return nil;";


    /**
     * <pre>
     *     释放锁
     * </pre>
     *
     * @param lockKey
     */
    public void unlock(String lockKey) {
        long threadId = Thread.currentThread().getId();
        List<String> keys = new ArrayList<>();
        keys.add(lockKey);
        Object[] args = new String[2];
        args[0] = String.valueOf(30 * 1000);
        args[1] = getLockName(threadId);
        DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>(LUA_UNLOCK, Boolean.class);
        redisTemplate.execute(redisScript, keys, args);
    }



    /**
     * 尝试获取锁
     * @param lockKey key
     * @param waitTimeUnit 最多等待时间单位
     * @param waitTime 最多等待时间
     * @param leaseTimeUnit 上锁后自动释放锁时间单位
     * @param leaseTime 上锁后自动释放锁时间
     * @return true ok or false
     */
    public boolean tryLock(String lockKey, TimeUnit waitTimeUnit, int waitTime, TimeUnit leaseTimeUnit, int leaseTime) {
        long time = waitTimeUnit.toMillis(waitTime);
        long lockLeaseTime= leaseTimeUnit.toMillis(leaseTime);
        final long threadId = Thread.currentThread().getId();
        try {
            while (true) {
                long current = System.currentTimeMillis();
                List<String> keys = new ArrayList<>();
                keys.add(lockKey);
                Object[] args = new String[2];
                args[0] = String.valueOf(lockLeaseTime);
                args[1] = getLockName(threadId);
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(LUA_LOCK, Long.class);
                Long ttl = redisTemplate.execute(redisScript, keys, args);
                if (ttl == null) {
                    return true;
                }
                Thread.sleep(50);
                time -= (System.currentTimeMillis() - current);
                if (time <= 0) {
                    return false;
                }
            }
        } catch (Exception ex) {
            log.error("获取锁失败:{}", ex);
            return false;
        }


    }

    private String getLockName(long threadId) {
        return id + ":" + threadId;
    }

}
